package it.aekidna.cleverplatypus.mediator
{
	import as3reflect.ClassUtils;
	import as3reflect.MetaData;
	import as3reflect.MetaDataArgument;
	import as3reflect.Method;
	import as3reflect.Type;
	
	import flash.utils.getQualifiedClassName;
	
	import it.aekidna.cleverplatypus.utils.IApplicationContextExposed;
	import it.aekidna.cleverplatypus.utils.IAutowirable;
	
	import mx.logging.ILogger;
	import mx.logging.Log;
	import mx.utils.DescribeTypeCache;
	import mx.utils.DescribeTypeCacheRecord;
	import mx.utils.StringUtil;
	
	import org.springextensions.actionscript.context.IApplicationContext;
	import org.springextensions.actionscript.context.IApplicationContextAware;
	
	public class MetadataUtil implements IApplicationContextAware
	{
		private static const LOGGER : ILogger = 
			Log.getLogger( getQualifiedClassName( MetadataUtil ).replace(/:+/g, "." ) );
		
		private static var _applicationContext : IApplicationContext;
		
		public static function autowireObject( inObject : Object, inContext : IApplicationContext = null ) : void
		{
			if( inContext == null )
			{
				if( _applicationContext )
					inContext = _applicationContext;
				else
					throw new Error( "Cannot autowire objects without a valid application context" );
			}
			if( inObject is IAutowirable )
			{
				LOGGER.debug(
					"Inspecting autowirable object: {0} ", inObject.toString() );	
			
				var typeInfo:DescribeTypeCacheRecord = DescribeTypeCache.describeType( inObject );
				for each (var metaDataNode:XML in typeInfo.typeDescription..metadata) 
				{
					if (metaDataNode.attribute("name") == "Autowired") 
					{
						var propertyNode:XML = metaDataNode.parent();
						var property:String = propertyNode.@name.toString();
						var objectName:String = property;
						var autowireByType:Boolean = true;
						for each (var arg:XML in metaDataNode.arg) 
						{
							if (arg.attribute("value") == "byName") 
							{
								autowireByType = false;
							}
						}
						if (autowireByType) 
						{
							var clazz:Class = ClassUtils.forName( propertyNode.@type.toString() );
							var objectNames:Array = inContext.getObjectNamesForType(clazz);
							if (objectNames.length == 1) 
							{
								objectName = objectNames[0];
							}
						}
						LOGGER.debug( 
							"Autowiring ({0}) {1}.{2} on {3}", 
							propertyNode.@type.toString(), 
							objectName,
							property,
							inObject.toString() );
						inObject[property] = inContext.getObject(objectName);
					}
				}
			}
		}

		public static function processMediateMetaTags( inObject : Object ) : void
		{
			if( inObject is IMediateable )
			{
				LOGGER.debug(
					"Inspecting mediatable object: {0} ", inObject.toString() );	
				var type : Type = Type.forInstance( inObject );
				for each( var method : Method in type.methods )
				{
					var tags : Array = method.getMetaData( "Mediate" );
					if( tags != null )
					{
						for each( var tag : MetaData in tags ) 
						{  
							
							var argsArg : MetaDataArgument = 
								tag.getArgument( "args" );
							var args : Array = [];
							if( argsArg != null ) 
							{
								args = StringUtil.trimArrayElements( argsArg.value, "," ).split( "," );
							}
							var registered : Boolean =
								MediationRegistry.getInstance().registerMediator(
									tag.getArgument("event").value,
									args,
									inObject[ method.name ],
									inObject
									 );
							if( !registered )
								return;
							var className : String = ClassUtils.getFullyQualifiedName(
								ClassUtils.forInstance( inObject ) );
							LOGGER.debug( 
								"Mediator created for method {0} triggered by event {1} on class {2}", 
								method.fullName, 
								tag.getArgument("event").value,
								className );
						}
					}
				}
			}
		}
		
		public function set applicationContext( inContext : IApplicationContext ) : void
		{
			_applicationContext = inContext;
		}
	}
}